﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using Bling.DSL;
using Bling.DataBinding;
using Bling.Shapes;
using Bling.Geom;
using Bling.Animation;
using Bling.WPF;
using System.Windows.Shapes;

namespace Bling.Examples {

  delegate void ClickEventHandler(Object sender, MouseEventArgs e);
  delegate void DragEventHandler(Object sender, MouseEventArgs e);

  /*
   * Custom Canvas that can be moved by clicking on it.
   */
  class MyScatterViewItem : Canvas {


    private bool moved = false;
    private Point oldPoint = new Point(0, 0);
    private bool isDown = false;

    public event ClickEventHandler Click = (x, y) => { };
    public event DragEventHandler Drag = (x, y) => { };
    public Image BgImage;

    public MyScatterViewItem(Image img) {




      BgImage = img;
      this.Children.Add(img);

      img.Dsl().CenterPosition = this.Dsl().CenterSize;

      var leftTop = img.Dsl().LeftTop;
      var size = img.Dsl().Size;
      this.Dsl().Size = leftTop.Underlying.Combine<Point, Transform, Point>(
        size, 
        img.Dsl<Transform>(Image.RenderTransformProperty), 
        (leftTop0, size0, transform) => {
          var size1 = (Point) transform.TransformBounds(new Rect(leftTop0, leftTop0 + (Vector)size0)).Size;
          return size1;
        }
      );


      this.MouseDown += (x, y) => {
        this.oldPoint = y.GetPosition((UIElement)this.Parent);
        moved = false;
        isDown = true;
      };
      
      this.MouseMove += (x, y) => {
        if (isDown) {
          var currPos = y.GetPosition((UIElement)this.Parent);
          //var centerPos = this.CenterPosition().Value;
          var delta = currPos - this.oldPoint;

          this.Dsl().LeftTop.Now += delta;
          this.oldPoint = currPos;
          moved = true;

          Drag(x, y);
        }

      };

      this.MouseUp += (x, y) => {

        if (!moved) {
          this.Click(x, y);
        }
        moved = false;
        isDown = false;

      };

      this.MouseLeave += (x, y) => {
        if (isDown && !moved) {

          this.Click(x, y);
        }
        moved = false;
        isDown = false;
      };

    }
  }


  class MultiShaderEffectCanvas : Canvas {
    private int maxZindex = 0;

    private static readonly TypedDependencyProperty<MultiShaderEffectCanvas, Point> RefPositionProperty = "RefPosition".RegisterDp<MultiShaderEffectCanvas, Point>();
    private static readonly TypedDependencyProperty<MultiShaderEffectCanvas, Point> RefSizeProperty = "RefSize".RegisterDp<MultiShaderEffectCanvas, Point>();

    //public Func<MyScatterViewItem, BaseLiftedShaderEffect> MyEffect;
    private Func<MyScatterViewItem, BrushDSL, PointDSL, ColorDSL> MyEffect;

    public readonly VisualBrush FrontBrush = new VisualBrush() {
      //AutoLayoutContent = false,
      Stretch = Stretch.Fill,
      TileMode = TileMode.None,
      ViewboxUnits = BrushMappingMode.RelativeToBoundingBox, Viewbox = new Rect(0, 0, 1, 1),
      ViewportUnits = BrushMappingMode.RelativeToBoundingBox, Viewport = new Rect(0, 0, 1, 1)

    };

    public readonly Slider slider = new Slider();

    public double duration = 1000;

    public MultiShaderEffectCanvas() {

      this.Background = new SolidColorBrush(Color.FromRgb(50, 50, 50));

      var r = new Random();

      
      maxZindex = 0;
      var item1 = new MyScatterViewItem(new Image() { Stretch = Stretch.Fill, Width = 250, Height = 250, Source = BlingExample.Properties.Resources.Autumn.LoadBitmap() });
      item1.Dsl().LeftTop = new Point(200, 300);
      item1.Dsl().ZIndex = maxZindex;
      item1.BgImage.Dsl().RenderTransform.RotateAt(item1.BgImage.Dsl().CenterSize).Bind = (r.NextDouble() * 360).Dsl().ToDegrees;
      item1.Click += new ClickEventHandler(item_MouseDown);
      item1.Drag += new DragEventHandler(item_Drag);
      this.Children.Add(item1);


      maxZindex++;
      var item2 = new MyScatterViewItem(new Image() { Stretch = Stretch.Fill, Width = 200, Height = 250, Source = BlingExample.Properties.Resources.Dock.LoadBitmap() });
      item2.Dsl().LeftTop = new Point(450, 250);
      item2.Dsl().ZIndex = maxZindex;
      item2.BgImage.Dsl().RenderTransform.Rotate = (r.NextDouble() * 360).Dsl().ToDegrees;
      item2.Click += new ClickEventHandler(item_MouseDown);
      item2.Drag += new DragEventHandler(item_Drag);
      this.Children.Add(item2);


      maxZindex++;
      var item3 = new MyScatterViewItem(new Image() { Stretch = Stretch.Fill, Width = 200, Height = 300, Source = BlingExample.Properties.Resources.Desert.LoadBitmap() });
      item3.Dsl().LeftTop = new Point(500, 400);
      //item2.Dsl().Size = new Point(353.55, 353.55);
      item3.Dsl().ZIndex = maxZindex;
      item3.BgImage.Dsl().RenderTransform.Rotate = (r.NextDouble() * 360).Dsl().ToDegrees;
      item3.Click += new ClickEventHandler(item_MouseDown);
      item3.Drag += new DragEventHandler(item_Drag);
      this.Children.Add(item3);
      

      slider.Dsl().Width = this.Dsl().Width;
      slider.Minimum = 0;
      slider.Maximum = 1;
      slider.Dsl().Bottom = this.Dsl().Height;
      slider.Dsl().Left = 0;
      slider.Dsl().ZIndex = 2;
      this.Children.Add(slider);
      slider.MouseEnter += (x, y) => {
        slider.Dsl().Value = slider.Dsl().Value;
        slider.Dsl().Value.Stop(false);

      };



      /**
       *  ---------
       *  | Input |
       *  | Image |
       *  |       |----- 
       *  ---------    |
       *       | Ref   |
       *       | Image |
       *       |       |
       *       ----------
       *  The Ref image is the clicked image which will be brought to the top
       */
      Func<MyScatterViewItem, BrushDSL, PointDSL, ColorDSL> curlingEffect = (child, input, uv) => {
        BrushDSL refImage = FrontBrush.Dsl();

        var refCenterPos = RefPositionProperty.Dsl(this) + RefSizeProperty.Dsl(this) / 2;

        var centerPos = child.Dsl().CenterPosition;

        // 
        // The dir and C define a line go through the center of input image, and the slope is 
        // perpendicular to the line from Ref image center to the Input image center.
        //
        var dir = (refCenterPos - centerPos).Normal;

        var C = -dir.Dot(new PointDSL(0.5,0.5));
        //
        // distance is the distance of the point in input image to the line defined by dir and C.
        //
        var distance = (uv.Dot(dir) + C);

        var offStart = child.Dsl().LeftTop - RefPositionProperty.Dsl(this);
        var offInClip = uv * child.Dsl().Size;
        var offInRef = offStart + offInClip;

        var uvInRef = offInRef / RefSizeProperty.Dsl(this);
        
        var isInsideRef =  (uvInRef.X >= 0.0 ) & (uvInRef.X <= 1.0 ) & (uvInRef.Y >= 0.0) & (uvInRef.Y <= 1.0);

        var maxDistance = (Math.Sqrt(2) / 2);

        distance = distance / maxDistance;

        //
        // sliderVal is ranged from 0 to 1.0, represent the percentage of the wipe edge in the whole wiping path.
        //
        var sliderVal = (slider.Dsl().Value - 0.5).Abs * 2.0;

        var a = distance - sliderVal;
        a = (a > 0).Condition(1.0, 0.0);

        //var candiColor = a.Lerp(input[uv], new ColorDSL(new Point3DDSL(0, 0, 0), 0));
        var candiColor = a.Lerp ( input[uv], ColorDSL.FromScRgb( 0, new Point3DDSL(0,0,0) ) );
        var colorInRef = isInsideRef.Condition(refImage[uvInRef], candiColor);

        colorInRef = colorInRef.ScA.Lerp( candiColor, colorInRef );

        var sliderDistance = sliderVal * maxDistance;
        var flipDistance = 2 * (sliderDistance - distance * maxDistance);
        //
        // uvInFlip is the mirror reflected position of the current pixel position, according to the edge wipe line.
        //
        var uvInFlip = uv + dir * flipDistance;
        var colorInFlip = ((uvInFlip.X >= 0.0) & (uvInFlip.X <= 1.0) & (uvInFlip.Y >= 0.0) & (uvInFlip.Y <= 1.0)).Condition(input[uvInFlip], input[uv]);
        colorInFlip = colorInFlip.ScA.Lerp ( input[uv], colorInFlip );

        var clr = a.Lerp ( colorInFlip, colorInRef );
        return clr;

      };

      MyEffect = curlingEffect;


    }

    private Point CalcWorldBounds(MyScatterViewItem canvas) {
      Point[] world_pt = CalcWorldPoint(canvas);
      double min_x = world_pt[0].X;
      double min_y = world_pt[0].Y;
      double max_x = min_x;
      double max_y = min_y;

      for (int i = 1; i < world_pt.Length; i++) {
        if (world_pt[i].X < min_x) min_x = world_pt[i].X;
        else if (world_pt[i].X > max_x) max_x = world_pt[i].X;

        if (world_pt[i].Y < min_y) min_y = world_pt[i].Y;
        else if (world_pt[i].Y > max_y) max_y = world_pt[i].Y;
      }

      return new Point(max_x - min_x, max_y - min_y);

    }

    private Point[] CalcWorldPoint(MyScatterViewItem canvas) {
      Point[] world_pt = new Point[4];
      Point[] local_pt = new Point[4];

      local_pt[0] = canvas.BgImage.Dsl().LeftTop.Value - (Vector)canvas.BgImage.Dsl().CenterPosition.Value;
      local_pt[1] = canvas.BgImage.Dsl().RightTop.Value - (Vector)canvas.BgImage.Dsl().CenterPosition.Value;
      local_pt[2] = canvas.BgImage.Dsl().RightBottom.Value - (Vector)canvas.BgImage.Dsl().CenterPosition.Value;
      local_pt[3] = canvas.BgImage.Dsl().LeftBottom.Value - (Vector)canvas.BgImage.Dsl().CenterPosition.Value;

      double angle = -canvas.BgImage.Dsl().RenderTransform.RotateAt(canvas.BgImage.Dsl().CenterSize).Degrees.Value;

      double c = Math.Cos(Math.PI * angle / 180.0);
      double s = Math.Sin(Math.PI * angle / 180.0);



      for (int i = 0; i < local_pt.Length; i++) {
        world_pt[i].X = c * local_pt[i].X + s * local_pt[i].Y;
        world_pt[i].Y = -s * local_pt[i].X + c * local_pt[i].Y;

        world_pt[i] += (Vector)canvas.Dsl().CenterPosition.Value;
      }
      return world_pt;
    }


    //
    // Separating axes test
    //
    private bool IsSeparating(Point[] rect1, Point[] rect2) {
      for (int i = 0; i < rect1.Length; i++) {
        if (IsSeparatingByLine(rect1[i], rect1[(i + 1) % rect1.Length], rect1[(i + 2) % rect1.Length], rect2))
          return true;
      }


      return false;
    }

    private bool IsSeparatingByLine(Point lineP1, Point lineP2, Point refPoint, Point[] testRect) {
      double refSide = CalcSide(lineP1, lineP2, refPoint);
      for (int i = 0; i < testRect.Length; i++) {
        //
        // if the testRect[i] is in the same side of refPoint
        //
        if (refSide * CalcSide(lineP1, lineP2, testRect[i]) >= 0)
          return false;
      }

      return true;
    }

    private double CalcSide(Point lineP1, Point lineP2, Point p) {
      Vector lineDir = (Vector)lineP2 - (Vector)lineP1;
      Vector lineDirP = new Vector(-lineDir.Y, lineDir.X);

      Vector line2 = p - lineP1;
      return lineDirP.X * line2.X + lineDirP.Y * line2.Y;
      //return lineDirP.DotProduct(p - lineP1);
    }

    //
    // test whether item1 and item2 are intersected.
    //
    private bool IsIntersect(Canvas item1, Canvas item2) {

      Point[] rect1 = CalcWorldPoint((MyScatterViewItem)item1);
      Point[] rect2 = CalcWorldPoint((MyScatterViewItem)item2);

      if (IsSeparating(rect1, rect2)) {
        return false;
      }

      if (IsSeparating(rect2, rect1)) {
        return false;
      }

      return true;


    }

    void item_Drag(object sender, MouseEventArgs e) {
      Canvas item = (Canvas)sender;
      if (item.Dsl().ZIndex.Value < maxZindex) {
        maxZindex++;
        item.Dsl().ZIndex.Stop(false);
        item.Dsl().ZIndex = maxZindex;
      }
    }

    void item_MouseDown(object sender, MouseEventArgs e) {
      MyScatterViewItem item = (MyScatterViewItem)(sender);

      bool isClip = false;

      FrontBrush.Visual = item.BgImage;
      RefPositionProperty.Dsl(this).Bind = item.Dsl().LeftTop;
      RefSizeProperty.Dsl(this).Bind = item.Dsl().Size;
#if false
      item.Effect = new DropShadowEffect() { Opacity = 0.5 };
      ((DropShadowEffect)item.Effect).Signal<double>(DropShadowEffect.ShadowDepthProperty).AnimateKey(
        new double[] { 0, 50, 0 }, new double[] { 0, 0.5, 1.0 }, a => a.Duration(duration));
#endif


      foreach (var c in this.Children) {
        if (c is Canvas) {
          MyScatterViewItem child = (MyScatterViewItem)c;

          if (child != item) {
            if (item.Dsl().ZIndex.Value <= child.Dsl().ZIndex.Value && IsIntersect(item, child)) {
              isClip = true;
              child.Dsl().CustomEffect = (input,uv)=> MyEffect(child, input, uv);
              //item.Effect = null;

            } else
              child.Effect = null;
          }
        }
      }
      if (isClip) {

        maxZindex++;

        slider.Dsl().Value.Stop(false);
        slider.Dsl().Value = 1d;

        //slider.Dsl().Value.AnimateTo(0, a => a.Duration(duration));
        slider.Dsl().Value.Animate.Duration(duration).To = 0;

        //if (MyEffect == curlingEffect)
        //item.Dsl().ZIndex().AnimateKey(new int[] { item.Zindex().Value, maxZindex }, new double[] { 0, 0.5 }, a => a.Duration(duration));
        
        item.Dsl().ZIndex.Animate.Duration(duration).Percents(0, 0.5).Keys(item.Dsl().ZIndex.Value, maxZindex);

#if false
        else {
          slider.Value().AnimateTo(0, a => {
            a.Duration(duration);
            a.Completed += (x2, y2) => {
              item.Zindex().Value = maxZindex;
              foreach (var c in Children) {
                if (c is MyScatterViewItem) {
                  ((MyScatterViewItem)c).Effect = null;
                }
              }
            };

            return a;
          }
          );
        }
#endif

      } else {
        if (item.Dsl().ZIndex.Value < maxZindex) {
          maxZindex++;
          item.Dsl().ZIndex.Stop(false);
          item.Dsl().ZIndex = maxZindex;
        }
      }
    }

    

  }

}